package parser;

import scanner.*;
import scanner.Scanner;

public class ProgramParser
    extends BaseParser
{
    public ProgramParser(Scanner scanner)
    {
        super(scanner);
        scanner.addObserver(this);
    }

    public SyntaxNode Parse()
    {
        SyntaxNode syntaxTree = new SyntaxNode(SyntaxNodeType.PROGRAM);
        ReadToken();
        syntaxTree.AddChild(ParseMainClass());
        PeekToken();
        if (currentToken.GetId() != TokenId.EOF && currentToken.GetId() != TokenId.COMMENT)
        {
            HandleError("Invalid statement syntax");
        }
        return syntaxTree;
    }

    public SyntaxNode ParseMainClass()
    {
        SyntaxNode mainClassNode = new SyntaxNode(SyntaxNodeType.MAIN_CLASS);
        ReadSpecificToken(TokenId.CLASS);
        mainClassNode.AddChild(ParseIdentifier());
        ReadToken();
        ReadSpecificToken(TokenId.LEFT_BRACE);
        ReadSpecificToken(TokenId.PUBLIC);
        ReadSpecificToken(TokenId.STATIC);
        ReadSpecificToken(TokenId.VOID);
        CheckMainWord();
        ReadToken();
        ReadSpecificToken(TokenId.LEFT_ROUND_BRACKET);
        ReadSpecificToken(TokenId.STRING);
        ReadSpecificToken(TokenId.LEFT_SQUARE_BRACKET);
        ReadSpecificToken(TokenId.RIGHT_SQUARE_BRACKET);
        mainClassNode.AddChild(ParseIdentifier());
        ReadToken();
        ReadSpecificToken(TokenId.RIGHT_ROUND_BRACKET);
        CheckSpecificToken(TokenId.LEFT_BRACE);
        PeekToken();
        if (currentToken.GetId() != TokenId.RIGHT_BRACE)
        {
            ReadToken();
            mainClassNode.AddChild(ParseStatementSequenceOpt());
        }

        ReadSpecificToken(TokenId.RIGHT_BRACE);
        ReadSpecificToken(TokenId.RIGHT_BRACE);
        return mainClassNode;
    }

    private SyntaxNode ParseStatementSequenceOpt()
    {
        SyntaxNode statementSequence = new SyntaxNode(SyntaxNodeType.STATEMENT_SEQUENCE);
        do
        {
            statementSequence.AddChild(ParseStatement());
        }
        while (currentToken.GetId() != TokenId.RIGHT_BRACE && !WasError());
        return statementSequence;
    }

    private SyntaxNode ParseStatement()
    {
        SyntaxNode statementNode = null;
        if (currentToken.GetId() == TokenId.SYSTEM)
        {
            statementNode = ParseSystemOutPrintln();
        }
        else if (currentToken.GetId() == TokenId.IDENTIFIER)
        {
            statementNode = ParseAssignment();
        }
        return statementNode;
    }

    private SyntaxNode ParseSystemOutPrintln()
    {
        SyntaxNode node = new SyntaxNode(SyntaxNodeType.OUTPUT);
        ReadSpecificToken(TokenId.SYSTEM);
        ReadSpecificToken(TokenId.DOT);
        ReadSpecificToken(TokenId.OUT);
        ReadSpecificToken(TokenId.DOT);
        ReadSpecificToken(TokenId.PRINTLN);
        ReadSpecificToken(TokenId.LEFT_ROUND_BRACKET);
        node.AddChild(ParseExpression());
        ReadSpecificToken(TokenId.RIGHT_ROUND_BRACKET);
        ReadSpecificToken(TokenId.SEMICOLON);
        return node;
    }

    private SyntaxNode ParseAssignment()
    {
        SyntaxNode assignmentNode = new SyntaxNode(SyntaxNodeType.ASSIGNMENT);
        assignmentNode.AddChild(ParseIdentifier());
        ReadToken();
        ReadSpecificToken(TokenId.ASSIGN);
        assignmentNode.AddChild(ParseExpression());
        ReadSpecificToken(TokenId.SEMICOLON);
        return assignmentNode;
    }

    private SyntaxNode ParseExpression()
    {
        ExpressionParser expressionParser = new ExpressionParser(scanner, currentToken);
        SyntaxNode expressionNode = expressionParser.Parse();
        ReadToken();
        return expressionNode;
    }

    private void CheckMainWord()
    {
        if (currentToken.GetId() != TokenId.IDENTIFIER || currentToken.GetData() == null || !currentToken.GetData().equals("main"))
        {
            HandleError("'main' expected");
        }
    }
}
